/**
 * 
 */
package any.desktop.server.impl;

import java.io.IOException;
import java.util.Iterator;

import org.apache.commons.lang.Validate;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Searcher;
import org.apache.lucene.store.Directory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import any.desktop.server.DesktopSearcher;

/**
 * @author keke <keke@codehaus.org>
 * @version
 * @revision $Revision:$
 */
public class DefaultDesktopSearcher implements DesktopSearcher {
	private static class HitsIterator implements Iterator<Document> {
		private int		   current;
		private final int	end;
		private final Hits	hits;

		public HitsIterator(final Hits hits, final int start, final int length) {
			super();
			this.hits = hits;
			if (LOG.isDebugEnabled()) {
				LOG.debug("Total documents is {}", hits.length());
			}
			current = start;
			end = start + length;

		}

		public boolean hasNext() {
			return hits.length() > current && current < end;
		}

		public Document next() {
			try {
				final Document doc = hits.doc(current);
				current++;
				return doc;
			} catch (final Exception e) {
				throw new RuntimeException(e);
			}

		}

		public void remove() {
			throw new UnsupportedOperationException();
		}
	}

	private static final Logger	LOG	= LoggerFactory
	                                        .getLogger(DefaultDesktopSearcher.class);

	private Directory	        directory;
	private IndexReader	        indexReader;
	private QueryParser	        queryParser;
	private Searcher	        searcher;

	public void destory() throws IOException {
		if (indexReader != null) {
			indexReader.close();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see any.desktop.server.DesktopSearcher#search(java.lang.String, int,
	 *      int)
	 */
	public Iterator<Document> search(final String query, final int start,
	        final int length) throws IOException {
		Validate.notNull(query, "Query String should not be null");
		if (LOG.isDebugEnabled()) {
			LOG.debug("To search {}", query);
		}
		try {
			final long st = System.currentTimeMillis();
			final Iterator<Document> iter = new HitsIterator(getSearcher()
			        .search(queryParser.parse(query)), start, length);
			final long et = System.currentTimeMillis();
			if (LOG.isInfoEnabled()) {
				LOG.info("A search was performed on query={}, cost={}",
				        new Object[] { query, et - st });
			}
			return iter;
		} catch (final ParseException e) {
			throw new RuntimeException("Unable to parse Query[" + query + "]",
			        e);
		}
	}

	public void setDirectory(final Directory directory) throws IOException {
		this.directory = directory;
		Validate.isTrue(IndexReader.indexExists(directory),
		        "Index is not existed");
		recreateSearcher();
	}

	public void setQueryParser(final QueryParser queryParser) {
		this.queryParser = queryParser;
	}

	private Searcher getSearcher() throws IOException {
		if (!indexReader.isCurrent()) {
			recreateSearcher();
		}
		return searcher;
	}

	private void recreateSearcher() throws CorruptIndexException, IOException {
		if (LOG.isDebugEnabled()) {
			LOG.debug("To recreate an Searcher");
		}
		if (indexReader == null) {
			indexReader = IndexReader.open(directory);
		} else {
			final IndexReader newIndex = indexReader.reopen();
			if (newIndex != indexReader) {
				indexReader.close();
			}
			indexReader = newIndex;
		}
		searcher = new IndexSearcher(indexReader);
	}

}
